﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
using Microsoft.IdentityModel.Protocols.WSTrust;
using Microsoft.IdentityModel.Protocols.WSTrust.Bindings;
using Microsoft.IdentityModel.SecurityTokenService;
using System.Web.Script.Serialization;
using Microsoft.IdentityModel.Tokens.Saml2;
using System.Xml;
using System.Net.Security;
using System.Globalization;
using System.Configuration;
using WifTokenTypes = Microsoft.IdentityModel.Tokens.SecurityTokenTypes;

namespace Tokens
{
   public static class AcsStsClient
   {
      public static SecurityToken GetToken(
         string hostname,
         string appliesTo,
         string keyType,
         StoreLocation storeLocation,
         StoreName storeName,
         X509FindType findType,
         Object findValue)
      {
         // Get the certificate.
         X509Certificate2 certificate;
         X509Store store = null;
         try
         {
            store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection certificates = store.Certificates.Find(findType, findValue, false);
            certificate = certificates[0];
         }
         finally
         {
            if (null != store) store.Close();
         }

         EndpointAddress address = new EndpointAddress("https://" + hostname + ".accesscontrol.windows.net/v2/wstrust/13/certificate");
         CertificateWSTrustBinding binding = new CertificateWSTrustBinding(SecurityMode.TransportWithMessageCredential);

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(binding, address);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.Credentials.ClientCertificate.Certificate = certificate;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannel();

               // Build the Request for Security Token (RST).
               RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue);
               rst.AppliesTo = new EndpointAddress(appliesTo);
               rst.KeyType = keyType;

               // Perform operation to receive the Request for Security Token Response (RSTR).
               RequestSecurityTokenResponse rstr = null;
               SecurityToken token = channel.Issue(rst, out rstr);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return token;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static SecurityToken GetToken(
         string hostname,
         string appliesTo,
         string keyType,
         X509Certificate2 certificate)
      {
         EndpointAddress address = new EndpointAddress("https://" + hostname + ".accesscontrol.windows.net/v2/wstrust/13/certificate");
         CertificateWSTrustBinding binding = new CertificateWSTrustBinding(SecurityMode.TransportWithMessageCredential);

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(binding, address);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.Credentials.ClientCertificate.Certificate = certificate;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannel();

               // Build the Request for Security Token (RST).
               RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue);
               rst.AppliesTo = new EndpointAddress(appliesTo);
               rst.KeyType = keyType;

               // Perform operation to receive the Request for Security Token Response (RSTR).
               RequestSecurityTokenResponse rstr = null;
               SecurityToken token = channel.Issue(rst, out rstr);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return token;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static SecurityToken GetToken(
         string hostname,
         string appliesTo,
         string keyType,
         string username,
         string password)
      {
         EndpointAddress acsStsEndpointAddress = new EndpointAddress("https://" + hostname + "/v2/wstrust/13/username");
         UserNameWSTrustBinding acsTrustBinding = new UserNameWSTrustBinding();
         acsTrustBinding.SecurityMode = SecurityMode.TransportWithMessageCredential;

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(acsTrustBinding, acsStsEndpointAddress);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.UserName.UserName = username;
            channelFactory.Credentials.UserName.Password = password;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannel();

               RequestSecurityToken rstACS = new RequestSecurityToken(RequestTypes.Issue);
               rstACS.AppliesTo = new EndpointAddress(appliesTo);
               rstACS.KeyType = KeyTypes.Bearer;

               SecurityToken nextToken = channel.Issue(rstACS);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return nextToken;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static SecurityToken GetTokenWithIssuedTokenSymmetric(
         string hostname,
         string appliesTo,
         string keyType,
         SecurityToken token)
      {
         EndpointAddress acsStsEndpointAddress = new EndpointAddress("https://" + hostname + "/v2/wstrust/13/issuedtoken-symmetric");
         IssuedTokenWSTrustBinding acsTrustBinding = new IssuedTokenWSTrustBinding();
         acsTrustBinding.SecurityMode = SecurityMode.TransportWithMessageCredential;
         acsTrustBinding.KeyType = SecurityKeyType.SymmetricKey;

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(acsTrustBinding, acsStsEndpointAddress);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannelWithIssuedToken(token);

               RequestSecurityToken rstACS = new RequestSecurityToken(RequestTypes.Issue);
               rstACS.AppliesTo = new EndpointAddress(appliesTo);
               rstACS.KeyType = keyType;

               SecurityToken nextToken = channel.Issue(rstACS);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return nextToken;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static SecurityToken GetTokenWithIssuedTokenAsymmetric(
         string hostname,
         string appliesTo,
         string keyType,
         SecurityToken token)
      {
         EndpointAddress acsStsEndpointAddress = new EndpointAddress("https://" + hostname + "/v2/wstrust/13/issuedtoken-asymmetric");
         IssuedTokenWSTrustBinding acsTrustBinding = new IssuedTokenWSTrustBinding();
         acsTrustBinding.SecurityMode = SecurityMode.TransportWithMessageCredential;
         acsTrustBinding.KeyType = SecurityKeyType.AsymmetricKey;

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(acsTrustBinding, acsStsEndpointAddress);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannelWithIssuedToken(token);

               RequestSecurityToken rstACS = new RequestSecurityToken(RequestTypes.Issue);
               rstACS.AppliesTo = new EndpointAddress(appliesTo);
               rstACS.KeyType = keyType;

               SecurityToken nextToken = channel.Issue(rstACS);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return nextToken;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static SecurityToken GetTokenWithIssuedTokenBearer(
         string hostname,
         string appliesTo,
         string keyType,
         SecurityToken token)
      {
         EndpointAddress acsStsEndpointAddress = new EndpointAddress("https://" + hostname + "/v2/wstrust/13/issuedtoken-bearer");
         IssuedTokenWSTrustBinding acsTrustBinding = new IssuedTokenWSTrustBinding();
         acsTrustBinding.SecurityMode = SecurityMode.TransportWithMessageCredential;
         acsTrustBinding.KeyType = SecurityKeyType.BearerKey;

         bool factoryClosed = false;
         WSTrustChannelFactory channelFactory = null;
         try
         {
            channelFactory = new WSTrustChannelFactory(acsTrustBinding, acsStsEndpointAddress);
            channelFactory.TrustVersion = TrustVersion.WSTrust13;
            channelFactory.Credentials.SupportInteractive = false;
            channelFactory.ConfigureChannelFactory();

            bool channelClosed = false;
            WSTrustChannel channel = null;
            try
            {
               channel = (WSTrustChannel)channelFactory.CreateChannelWithIssuedToken(token);

               RequestSecurityToken rstACS = new RequestSecurityToken(RequestTypes.Issue);
               rstACS.AppliesTo = new EndpointAddress(appliesTo);
               rstACS.KeyType = keyType;

               SecurityToken nextToken = channel.Issue(rstACS);

               channel.Close();
               channelClosed = true;
               channelFactory.Close();
               factoryClosed = true;
               return nextToken;
            }
            finally
            {
               if ((null != channel) && (!channelClosed)) channel.Abort();
            }
         }
         finally
         {
            if ((null != channelFactory) && (!factoryClosed)) channelFactory.Abort();
         }
      }

      public static string GetSwtToken(
         string hostname,
         string appliesTo,
         string username,
         string password)
      {
         try
         {
            WebClient client = new WebClient();
            client.BaseAddress = "https://" + hostname + "/";

            NameValueCollection parameters = new NameValueCollection();
            // ensure the wrap_scope URI is created in your ACS
            // service namespace
            parameters.Add("wrap_name", username);
            parameters.Add("wrap_password", password);
            parameters.Add("wrap_scope", appliesTo);

            byte[] responseBytes = client.UploadValues("WRAPv0.9/", "POST", parameters);
            string response = Encoding.UTF8.GetString(responseBytes);

            return response
               .Split('&')
               .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
               .Split('=')[1];
         }
         catch (WebException wex)
         {
            string errorMessage = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
            throw new InvalidOperationException(errorMessage, wex);
         }
      }

      public static string GetSwtToken2(
         string hostname,
         string grant_type,
         string scope,
         string client_id,
         string client_secret)
      {
         byte[] responseBytes = RequestAccessToken(
            hostname,
            grant_type,
            scope,
            client_id,
            client_secret);
         string response = Encoding.UTF8.GetString(responseBytes);

         JavaScriptSerializer serializer = new JavaScriptSerializer();
         Dictionary<string, object> decodedDictionary =
            serializer.DeserializeObject(response) as Dictionary<string, object>;

         //return HttpUtility.HtmlDecode(decodedDictionary["access_token"] as string);
         return decodedDictionary["access_token"] as string;
      }

      public static byte[] RequestAccessToken(
         string hostname,
         string grant_type,
         string scope,
         string client_id,
         string client_secret)
      {
         try
         {
            WebClient client = new WebClient();
            client.BaseAddress = "https://" + hostname + "/";

            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("scope", scope.ToLower());
            parameters.Add("grant_type", grant_type);
            parameters.Add("client_id", client_id.ToLower());
            parameters.Add("client_secret", client_secret);

            return client.UploadValues("v2/OAuth2-13", "POST", parameters);
         }
         catch (WebException wex)
         {
            string errorMessage = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
            throw new InvalidOperationException(errorMessage, wex);
         }
      }

      public static string GetSwtToken2(
         string hostname,
         string grant_type,
         string scope,
         string client_id,
         byte[] pfxBytes,
         string pfxPassword)
      {
         byte[] responseBytes = RequestAccessToken(
            hostname,
            grant_type,
            scope,
            client_id,
            pfxBytes,
            pfxPassword);
         string response = Encoding.UTF8.GetString(responseBytes);

         JavaScriptSerializer serializer = new JavaScriptSerializer();
         Dictionary<string, object> decodedDictionary =
            serializer.DeserializeObject(response) as Dictionary<string, object>;

         //return HttpUtility.HtmlDecode(decodedDictionary["access_token"] as string);
         return decodedDictionary["access_token"] as string;
      }

      public static byte[] RequestAccessToken(
         string hostname,
         string grant_type,
         string scope,
         string client_id,
         byte[] pfxBytes,
         string pfxPassword)
      {
         try
         {
            Saml2SecurityToken token = SelfSignedSaml2TokenGenerator.GetSamlAssertionSignedWithCertificate(
               hostname, client_id, pfxBytes, pfxPassword);

            WebClient client = new WebClient();
            client.BaseAddress = "https://" + hostname + "/";

            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("scope", scope.ToLower());
            parameters.Add("grant_type", grant_type);
            parameters.Add("assertion", GetSaml2TokenString(token));

            return client.UploadValues("v2/OAuth2-13", "POST", parameters);
         }
         catch (WebException wex)
         {
            string errorMessage = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
            throw new InvalidOperationException(errorMessage, wex);
         }
      }

      static string GetSaml2TokenString(Saml2SecurityToken token)
      {
         XmlWriterSettings writerSettings = new XmlWriterSettings();
         StringBuilder sb = new StringBuilder();

         writerSettings.OmitXmlDeclaration = true;

         using (XmlWriter xw = XmlWriter.Create(sb, writerSettings))
         {
            new Saml2SecurityTokenHandler().WriteToken(xw, token);

            return sb.ToString();
         }
      }
   }
}
